package sg.edu.nus.iss.ooad.jass.dao.file;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;

/**
 *
 * Abstract class for Properties Data Access Object. 
 *
 * @author Isak Rabin (HT082193M)
 * @version 1.0
 * @since 1.0
 *
 * <p>
 * Development Environment : Netbeans IDE v6.7<br/>
 * Name of the File        : AbstractPropertieDAO.java<br/>
 * Creation / Modification History<br/>
 * <ul>
 *      <li>Isak Rabin        20-Sep-2009        Created</li>
 * </ul>
 * </p>
 *
 */
public abstract class AbstractPropertiesDAO {

        private final static boolean THROW_ON_LOAD_FAILURE = true;
        private final static boolean LOAD_AS_RESOURCE_BUNDLE = false;
        private final static String SUFFIX = ".properties";

        /**
         * Looks up a resource named 'name' in the classpath. The resource must map
         * to a file with .properties extention. The name is assumed to be absolute
         * and can use either "/" or "." for package segment separation with an
         * optional leading "/" and optional ".properties" suffix. Thus, the
         * following names refer to the same resource:
         * <pre>
         * some.pkg.Resource
         * some.pkg.Resource.properties
         * some/pkg/Resource
         * some/pkg/Resource.properties
         * /some/pkg/Resource
         * /some/pkg/Resource.properties
         * </pre>
         *
         * @param name Classpath resource name [may not be null]
         * @param loader Classloader through which to load the resource [null
         * is equivalent to the application loader]
         *
         * @return resource converted to java.util.Properties [may be null if the
         * resource was not found and THROW_ON_LOAD_FAILURE is false]
         * @throws IllegalArgumentException if the resource was not found and
         * THROW_ON_LOAD_FAILURE is true
         */
        public static synchronized Properties loadProperties(String name, ClassLoader loader) {
                if (name == null) {
                        throw new IllegalArgumentException("Null input: name");
                }

                if (name.startsWith("/")) {
                        name = name.substring(1);
                }

                if (name.endsWith(SUFFIX)) {
                        name = name.substring(0, name.length() - SUFFIX.length());
                }

                InputStream in = null;
                Properties properties = null;
                try {
                        if (loader == null) {
                                loader = ClassLoader.getSystemClassLoader();
                        }

                        if (LOAD_AS_RESOURCE_BUNDLE) {
                                name = name.replace('/', '.');

                                // Throws MissingResourceException on lookup failures:
                                ResourceBundle rb = ResourceBundle.getBundle(name, Locale.getDefault(), loader);
                                properties = new Properties();
                                for (Enumeration keys = rb.getKeys(); keys.hasMoreElements();) {
                                        String key = (String) keys.nextElement();
                                        String value = rb.getString(key);
                                        properties.put(key, value);
                                }

                        } else {

                                name = name.replace('.', '/');
                                if (!name.endsWith(SUFFIX)) {
                                        name = name.concat(SUFFIX);
                                }

                                // Returns null on lookup failures:
                                in = loader.getResourceAsStream(name);
                                if (in != null) {
                                        properties = new Properties();
                                        properties.load(in); // Can throw IOException
                                }
                        }
                } catch (Exception e) {
                        properties = null;
                } finally {
                        if (in != null) {
                                try {
                                        in.close();
                                } catch (Throwable ignore) {
                                }
                        }
                }

                if (THROW_ON_LOAD_FAILURE && (properties == null)) {
                        throw new IllegalArgumentException("could not load [" + name + "]" +
                                " as " + (LOAD_AS_RESOURCE_BUNDLE
                                ? "a resource bundle"
                                : "a classloader resource"));
                }

                return properties;
        }

        /**
         * A convenience overload of {@link #loadProperties(String, ClassLoader)}
         * that uses the current thread's context classloader.
         * @param name
         * @return
         */
        public static synchronized Properties loadProperties(String name) {
                return loadProperties(name, Thread.currentThread().getContextClassLoader());
        }

        /**
         * Looks up a resource named 'name' in the classpath. Map the resource (java.util.properties) to
         * to a file with .properties extention. The name is assumed to be absolute
         * and can use either "/" or "." for package segment separation with an
         * optional leading "/" and optional ".properties" suffix. Thus, the
         * following names refer to the same resource:
         * <pre>
         * some.pkg.Resource
         * some.pkg.Resource.properties
         * some/pkg/Resource
         * some/pkg/Resource.properties
         * /some/pkg/Resource
         * /some/pkg/Resource.properties
         * </pre>
         *
         * @param properties Value of java.util.Properties to be converted to persistent object [may no be null].
         * @param name Classpath resource name [may not be null]
         * @param loader Classloader through which to load the resource [null is equivalent to the application loader]
         * @return True if the resource succesfully created.
         * @throws IllegalArgumentException if failed to create the resource and THROW_ON_LOAD_FAILURE is true
         */
        public static synchronized boolean storeProperties(Properties properties, String name, ClassLoader loader) {
                boolean retValue = true;

                if (name == null) {
                        throw new IllegalArgumentException("null input: name");
                }

                if (name.startsWith("/")) {
                        name = name.substring(1);
                }

                if (name.endsWith(SUFFIX)) {
                        name = name.substring(0, name.length() - SUFFIX.length());
                }

                OutputStream out = null;
                try {

                        if (loader == null) {
                                loader = ClassLoader.getSystemClassLoader();
                        }

                        if (LOAD_AS_RESOURCE_BUNDLE) {
                                name = "resources." + name.replace('/', '.');
                        } else {
                                name = "resources/" + name.replace('.', '/');
                                if (!name.endsWith(SUFFIX)) {
                                        name = name.concat(SUFFIX);
                                }
                        }

                        //URL url = ClassLoader.getSystemResource(name);
                        out = new FileOutputStream(name);
                        properties.store(out, name);
                        out.close();
                } catch (Exception e) {
                       retValue =false;
                       e.printStackTrace();
                } finally {
                        if (out != null) {
                                try {
                                        out.close();
                                } catch (Throwable ignore) { 
                                }
                        }
                } 
                return retValue;
        }

        /**
         * A convenience store of {@link #storeProperties(Properties,String, ClassLoader)}
         * that uses the current thread's context classloader.
         *
         * @param properties
         * @param name
         * @return
         */
        public static synchronized boolean storeProperties(Properties properties, String name) {
                return storeProperties(properties, name, Thread.currentThread().getContextClassLoader());
        }
}

